// <copyright file="Table.cs" company="Seditious Software">
// Seditious Software 2011, MIT Licenced (http://www.opensource.org/licenses/mit-license)
// </copyright>
// <author>Tristan McNab</author>
// <date>2011-03-04</date>
namespace Seditious.NETMF.MuDB
{
    using System;
    using System.Collections;
    using System.Diagnostics;
    using System.Reflection;
    using Microsoft.SPOT;

    /// <summary>
    /// Represents a 2D Table of the specified type, where columns are defined by the type and 
    /// rows contain table data.
    /// </summary>
    [Serializable]
    public class Table
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the Table class. This constructor should be used
        /// if creating a new table from scratch.
        /// </summary>
        /// <param name="type">The object type to serve as the table schema.</param>
        public Table(Type type)
        {
            this.IsValidType(type);
            this.Type = type;
            this.Data = new ArrayList();
        }

        /// <summary>
        /// Initializes a new instance of the Table class. This constructor should be used
        /// if recreating a previous instance of the table.
        /// </summary>
        /// <param name="type">The object type to serve as the table schema.</param>
        /// <param name="data">The data associated with the table.</param>
        public Table(Type type, ArrayList data)
        {
            this.Type = type;
            this.Data = data;
        }

        #endregion

        #region Class Data

        /// <summary>
        /// Gets the table's "schema".
        /// </summary>
        public Type Type { get; private set; }   // Table "Schema"

        /// <summary>
        /// Gets the entirety of the table's data. Serves as the store.
        /// </summary>
        public ArrayList Data { get; private set; }   // Table Data

        #endregion

        #region Debugging Assist Code

        /// <summary>
        /// Prints the formatted table schema to the debugger.
        /// </summary>
        /// <param name="table">The table to display.</param>
        [Conditional("DEBUG")]
        public static void DumpSchema(Table table)
        {
            Debug.Print("TABLE " + table.Type.Name + " {");
            FieldInfo[] info = table.Type.GetFields();
            for (int i = 0; i < info.Length; i++)
            {
                Debug.Print("\t" + info[i].Name + " (" + info[i].FieldType.ToString() + ") ");
            }

            Debug.Print("}\n");
        }

        /// <summary>
        /// Prints the table data to the debugger
        /// </summary>
        /// <param name="table">The table to display.</param>
        [Conditional("DEBUG")]
        public static void DumpData(Table table)
        {
            foreach (var item in table.Data)
            {
                Debug.Print(item.ToString());
            }
        }

        #endregion

        #region Operations

        /// <summary>
        /// Inserts an object of the table type into the table
        /// </summary>
        /// <param name="row">The object to insert.</param>
        public void Insert(object row)
        {
            // Check the type & add if of same type
            if (this.Type.IsInstanceOfType(row))
            {
                this.Data.Add(row);
            }
            else
            {
                // If not the same type then throw an exception
                throw new DatabaseException("Argument 'row' of type '" + row.GetType().FullName + "' is not same as table type '" + this.Type.FullName + "'.");
            }
        }

        /// <summary>
        /// Delete an object 
        /// </summary>
        /// <param name="row">The object to remove from the table.</param>
        public void Delete(object row)
        {
            // Check the type & add if of same type
            if (this.Type.IsInstanceOfType(row))
            {
                this.Data.Remove(row);
            }
            else
            {
                // If not the same type then throw an exception
                throw new ArgumentException("Argument 'row' of type " + row.GetType().Name + " is not same as table type " + this.Type.Name);
            }
        }

        #endregion

        #region Support Methods

        /// <summary>
        /// Determines whether a column exists in the current table schema.
        /// </summary>
        /// <param name="fieldname">The column name to check.</param>
        /// <returns>True if found, false if not found.</returns>
        protected bool FieldExists(string fieldname)
        {
            bool wasFound = false;
            foreach (FieldInfo field in this.Type.GetFields())
            {
                if (field.Name == fieldname)
                {
                    wasFound = true;
                }
            }

            return wasFound;
        }

        /// <summary>
        /// Determines whether or nor the current type 
        /// </summary>
        /// <param name="type">The type to evaluate.</param>
        /// <exception cref="MuDB.DatabaseException"></exception>
        protected void IsValidType(Type type)
        {
            // Make sure the type is marked serializable
            if (!type.IsSerializable)
            {
                throw new DatabaseException("Type '" + type.Name + "' is not marked Serializable (System.SerializableAttribute).");
            }

            // Make sure that it has more than zero public instance fields
            if (type.GetFields(BindingFlags.Public | BindingFlags.Instance).Length == 0)
            {
                throw new DatabaseException("Type '" + type.Name + "' does not have any public instance fields.");
            }
        }

        #endregion
    }
}